package com.zomiot.portal.auth.config;

import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import com.zomiot.portal.auth.constant.UserPermission;
import com.zomiot.portal.auth.constant.UserRole;
import com.zomiot.portal.auth.constant.UserState;
import com.zomiot.portal.user.dto.UserInfoVO;
import com.zomiot.portal.user.entity.UserPO;
import com.zomiot.portal.user.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * Sa-Token权限验证接口实现类
 *
 * 这个类会在用户访问需要权限的接口时被调用
 * 用于获取用户的权限列表和角色列表
 *
 * @author Joash_Yi
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class StpInterfaceImpl implements StpInterface {

    private final UserMapper userMapper;

    /**
     * 获取用户的权限列表
     *
     * 这个方法会在调用StpUtil.checkPermission()时被触发
     *
     * @param loginId   登录用户ID
     * @param loginType 登录类型（默认为"login"）
     * @return 权限列表
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // 安全地获取用户信息（带空值检查和数据库回退）
        UserInfoVO userInfo = getUserInfoSafely(loginId);

        if (userInfo == null) {
            log.warn("无法获取用户信息（Session和数据库均未找到）：loginId={}", loginId);
            return List.of();
        }

        List<String> permissions = new ArrayList<>();

        // 根据用户状态和角色分配权限
        if (userInfo.getState() == UserState.DISABLED) {
            // 被禁用的用户没有任何权限
            log.debug("用户已被禁用：userId={}", loginId);
            return List.of();
        }

        // 根据角色分配权限（权限递进）
        if (userInfo.getRole() == UserRole.SYSTEM_ADMIN) {
            // 系统管理员拥有所有权限
            permissions.add(UserPermission.GUEST.name());
            permissions.add(UserPermission.MEMBER.name());
            permissions.add(UserPermission.EDITOR.name());
            permissions.add(UserPermission.ADMIN.name());
        } else if (userInfo.getRole() == UserRole.CONTENT_MANAGER) {
            // 内容管理员拥有编辑权限
            permissions.add(UserPermission.GUEST.name());
            permissions.add(UserPermission.MEMBER.name());
            permissions.add(UserPermission.EDITOR.name());
        } else if (userInfo.getRole() == UserRole.MEMBER) {
            // 普通会员拥有会员权限
            permissions.add(UserPermission.GUEST.name());
            permissions.add(UserPermission.MEMBER.name());
        } else {
            // 默认只有访客权限
            permissions.add(UserPermission.GUEST.name());
        }

        log.debug("用户权限列表：userId={}, permissions={}", loginId, permissions);
        return permissions;
    }

    /**
     * 获取用户的角色列表
     *
     * 这个方法会在调用StpUtil.checkRole()时被触发
     *
     * @param loginId   登录用户ID
     * @param loginType 登录类型（默认为"login"）
     * @return 角色列表
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        // 安全地获取用户信息（带空值检查和数据库回退）
        UserInfoVO userInfo = getUserInfoSafely(loginId);

        if (userInfo == null) {
            log.warn("无法获取用户信息（Session和数据库均未找到）：loginId={}", loginId);
            return List.of();
        }

        // 返回用户的角色
        List<String> roles = List.of(userInfo.getRole().name());

        log.debug("用户角色列表：userId={}, roles={}", loginId, roles);
        return roles;
    }

    /**
     * 安全地从Session或数据库获取用户信息
     *
     * 获取逻辑：
     * 1. 优先从Session中获取（性能最优）
     * 2. 如果Session中没有，从数据库加载
     * 3. 将数据库中的数据回写到Session（避免下次再查数据库）
     *
     * @param loginId 登录用户ID
     * @return 用户信息，如果用户不存在则返回null
     */
    private UserInfoVO getUserInfoSafely(Object loginId) {
        // 1. 获取Session（可能为null）
        SaSession session = StpUtil.getSessionByLoginId(loginId);

        if (session == null) {
            log.warn("Session不存在，尝试从数据库加载用户信息：loginId={}", loginId);
            return loadUserFromDatabase(loginId);
        }

        // 2. 从Session中获取用户信息
        UserInfoVO userInfo = (UserInfoVO) session.get(loginId.toString());

        if (userInfo != null) {
            // Session中有数据，直接返回
            return userInfo;
        }

        // 3. Session存在但没有用户信息，从数据库加载
        log.warn("Session中未找到用户信息，尝试从数据库加载：loginId={}", loginId);
        return loadUserFromDatabase(loginId);
    }

    /**
     * 从数据库加载用户信息并更新到Session
     *
     * @param loginId 登录用户ID
     * @return 用户信息，如果用户不存在则返回null
     */
    private UserInfoVO loadUserFromDatabase(Object loginId) {
        try {
            // 转换为Long类型
            Long userId = Long.valueOf(loginId.toString());

            // 从数据库查询用户
            UserPO user = userMapper.selectById(userId);

            if (user == null) {
                log.error("数据库中未找到用户：userId={}", userId);
                return null;
            }

            // 转换为VO
            UserInfoVO userInfo = convertToVO(user);

            // 回写到Session（如果Session存在的话）
            try {
                SaSession session = StpUtil.getSessionByLoginId(loginId);
                if (session != null) {
                    session.set(loginId.toString(), userInfo);
                    log.info("用户信息已从数据库加载并更新到Session：userId={}", userId);
                }
            } catch (Exception e) {
                log.error("更新Session失败：userId={}, error={}", userId, e.getMessage());
                // 不影响返回结果，继续返回用户信息
            }

            return userInfo;

        } catch (NumberFormatException e) {
            log.error("loginId格式错误：loginId={}", loginId, e);
            return null;
        } catch (Exception e) {
            log.error("从数据库加载用户信息失败：loginId={}", loginId, e);
            return null;
        }
    }

    /**
     * 实体转VO
     *
     * @param entity 用户实体
     * @return 用户VO
     */
    private UserInfoVO convertToVO(UserPO entity) {
        return UserInfoVO.builder()
                .userId(entity.getId())
                .username(entity.getUsername())
                .displayName(entity.getDisplayName())
                .telephone(entity.getTelephone())
                .email(entity.getEmail())
                .role(entity.getRole())
                .state(entity.getState())
                .createTime(entity.getCreateTime())
                .lastLoginTime(entity.getLastLoginTime())
                .build();
    }
}